package com.djhu.es.core;

import com.djhu.es.core.request.AddRequest;
import com.djhu.es.core.request.IdRequest;
import com.djhu.es.core.request.SearchRequest;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.unit.TimeValue;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class ElasticsearchTemplate implements ElasticsearchOperations{

    //游标 默认保持 三分钟。。
    private int defaultScrolltime = 300;

    //一次查询结果集的大小 默认5k  es默认最大也只有1w、、
    private Integer resultsize = 5000;
    private ClientSource clientSource;

    public ElasticsearchTemplate(ClientSource clientSource) {
        this.clientSource = clientSource;
    }
    @Override
    public Map<String,Object> get(IdRequest request) throws Exception {
        Client client = null;
        try {
            client = clientSource.getClient();
            GetResponse getResponse = client.prepareGet(request.index(),request.type(),request.id()).get(new TimeValue(request.timeout()));
            return getResponse.getSourceAsMap();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }

    @Override
    public <T> T get(IdRequest request, Class<T> tClass) throws Exception {
        Map<String,Object> map = get(request);
        PropertyBeanMapper<T> propertyBeanMapper = new PropertyBeanMapper<>(tClass);
        return propertyBeanMapper.mapRow(map,map.size());
    }

    @Override
    public DeleteResponse del(IdRequest request) throws Exception {
        Client client = null;
        try {
            client = clientSource.getClient();
            return client.prepareDelete(request.index(),request.type(),request.id()).execute().get();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }


    @Override
    public IndexResponse add(AddRequest request) throws Exception{
        Client client = null;
        try {
            client = clientSource.getClient();
            IndexResponse response = null;
            if (StringUtils.isEmpty(request.id())){
                response = client.prepareIndex(request.index(),request.type()).setSource(JSONObject.fromObject(request.value())).get(new TimeValue(request.timeout()));
            }else {
                response = client.prepareIndex(request.index(),request.type(),request.id()).setSource(JSONObject.fromObject(request.value())).get(new TimeValue(request.timeout()));
            }
            return response;
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }



    @Override
    public Map queryforMap(SearchRequest searchRequest) throws Exception {
        List<Map<String,Object>> list = queryforList(searchRequest);
        return requiredSingleResult(list);
    }


    @Override
    public <T>  List<T> queryforlist(SearchRequest searchRequest, Class<T> tClass) throws Exception{
        Client client = null;
        try {
            client = clientSource.getClient();
            ResultListExtract<T> resultExtractor = new ResultListExtract<T>(new PropertyBeanMapper<T>(tClass),client);
            List<T> list =  execute(searchRequest,resultExtractor);
            return list;
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }

    @Override
    public <T>  List<T> queryforlist(SearchRequest searchRequest, RowMapper<T> rowMapper) throws Exception{
        Client client = null;
        try {
            client = clientSource.getClient();
            ResultListExtract<T> resultExtractor = new ResultListExtract<T>(rowMapper,client);
            List<T> list =  execute(searchRequest,resultExtractor);
            return list;
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }

    @Override
    public List<Map<String, Object>> queryforList(SearchRequest searchRequest) throws Exception {
        Client client = null;
        try {
            client = clientSource.getClient();
            MapRowMapper mapRowMapper = new MapRowMapper();
            ResultListExtract<Map<String, Object>> resultExtractor = new ResultListExtract<Map<String, Object>>(mapRowMapper,client);
            List<Map<String, Object>> list =  execute(searchRequest,resultExtractor);
            return list;
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }

    @Override
    public <T> T queryForObject(SearchRequest searchRequest, Class<T> tClass) throws Exception {
        RowMapper<T> rowMapper = new PropertyBeanMapper(tClass);
        return queryForObject(searchRequest,rowMapper);
    }


    @Override
    public <T> T queryForObject(SearchRequest searchRequest, RowMapper<T> rowMapper) throws Exception {
        Client client = null;
        try {
            client = clientSource.getClient();
            ResultListExtract<T> resultExtractor = new ResultListExtract<T>(rowMapper,client);
            List<T> list =  execute(searchRequest,resultExtractor);
            return requiredSingleResult(list);
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }

    @Override
    public <T> T execute(SearchRequest searchRequest, SearchResponseCallback<T> action) throws Exception{
        Client client = null;
        try {
            client = clientSource.getClient();
            SearchRequestBuilder builder = client.prepareSearch(searchRequest.index());
            if (!StringUtils.isEmpty(searchRequest.type())){
                builder.setTypes(searchRequest.type());
            }
            SearchResponse searchResponse =  builder.setScroll(new TimeValue(defaultScrolltime,TimeUnit.SECONDS)).setQuery(searchRequest.queryBuilder()).setSize(resultsize).get(new TimeValue(searchRequest.timeout()));
            return action.action(searchResponse);
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            clientSource.returnResource(client);
        }
    }

    @Override
    public <T> T execute(final SearchRequest searchRequest, final ResultExtractor<T> rse)  throws Exception{
        class InnerSearchResponseCallback implements SearchResponseCallback<T>{
            private ResultExtractor resultExtractor;

            public InnerSearchResponseCallback(ResultExtractor resultExtractor) {
                this.resultExtractor = resultExtractor;
            }

            @Override
            public T action(SearchResponse searchResponse) throws Exception {
               return rse.extractData(searchResponse);
            }
        }
        return execute(searchRequest,new InnerSearchResponseCallback(rse));

    }




    public  <T> T requiredSingleResult(Collection<T> results){
        if(CollectionUtils.isEmpty(results)) {
            throw new RuntimeException("results is null");
        } else if(results.size() > 1) {
            throw new RuntimeException("results is " + results.size());
        } else {
            return results.iterator().next();
        }
    }

    public Integer getResultsize() {
        return resultsize;
    }

    public void setResultsize(Integer resultsize) {
        this.resultsize = resultsize;
    }

    public int getDefaultScrolltime() {
        return defaultScrolltime;
    }

    public void setDefaultScrolltime(int defaultScrolltime) {
        this.defaultScrolltime = defaultScrolltime;
    }
}
